/* Copyright (C) 2010 Fredrique Samuels, fredriquesamuels@gmail.com 

 This program is free software: you can redistribute it and/or modify
 it under the terms of the GNU General Public License as published by
 the Free Software Foundation, either version 3 of the License, or
 (at your option) any later version.

 This program is distributed in the hope that it will be useful,
 but WITHOUT ANY WARRANTY; without even the implied warranty of
 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 GNU General Public License for more details.

 You should have received a copy of the GNU General Public License
 along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */


#include "mesh.h"

namespace tri{

TTexCoord::TTexCoord(float u,float v){
	this->u = u;
	this->v = v;
}

TPolygonFace::TPolygonFace(){}

void TPolygonFace::addData(unsigned int v, unsigned int vt , unsigned int vn){
	mVertexIndices.push_back(v);
	mTexCoordIndices.push_back(vt);
	mNormalIndices.push_back(vn);
}

unsigned int TPolygonFace::size() const{
	return mVertexIndices.size();
}
unsigned int TPolygonFace::getVertex(unsigned int index){
	return mVertexIndices[index];
}

unsigned int TPolygonFace::getNormal(unsigned int index){
	return mNormalIndices[index];
}

unsigned int TPolygonFace::getTexCoord(unsigned int index){
	return mTexCoordIndices[index];
}

//==========================================================


unsigned int TMesh::CELL_SHADING_TEXTURE_ID = 0;

void TMesh::initCellShadingTexture(){
	if(TMesh::CELL_SHADING_TEXTURE_ID == 0){
		glEnable(GL_TEXTURE_1D);
		
		glGenTextures (1, &TMesh::CELL_SHADING_TEXTURE_ID);						// Get A Free Texture ID ( NEW )

		glBindTexture (GL_TEXTURE_1D, TMesh::CELL_SHADING_TEXTURE_ID);			// Bind This Texture. From Now On It Will Be 1D ( NEW )

		// For Crying Out Loud Don't Let OpenGL Use Bi/Trilinear Filtering! ( NEW )
		glTexParameteri (GL_TEXTURE_1D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);	
		glTexParameteri (GL_TEXTURE_1D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
		unsigned char  shader_data[32*4];
		for(int i = 0; i < 4 ;i++){
			shader_data[i*3] = 0;
			shader_data[i*3+1] = 0;
			shader_data[i*3+2] = 0;
			shader_data[i*3+3] = 100;
		}
		
		for(int i = 10; i < 25 ;i++){
			shader_data[i*3] = 0;
			shader_data[i*3+1] = 0;
			shader_data[i*3+2] = 0;
			shader_data[i*3+3] = 100;
		}
		
		for(int i = 25; i < 32 ;i++){
			shader_data[i*3] = 0;
			shader_data[i*3+1] = 0;
			shader_data[i*3+2] = 0;
			shader_data[i*3+3] = 100;
		}
		//float  shader_data[] = {0.2,0.2,0.2,0.2,0.5,0.5,0.5,0.5,0.5,0.5,0.5,0.5,0.5,0.5,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1};
		glTexImage1D (GL_TEXTURE_1D, 0, GL_RGBA, 32, 0, GL_RGBA , GL_UNSIGNED_BYTE, shader_data);
	}
}

void TMesh::bindShader(){
	glBindTexture (GL_TEXTURE_1D, TMesh::CELL_SHADING_TEXTURE_ID);
}

TMesh::TMesh(){
	setUIDLoad(false);
	mDisplayID = 0;
}

TMesh::~TMesh(){clear();}

void TMesh::clear(){
	mVertexBuffer.clear();
    mNormalBuffer.clear();
    mFaceBuffer.clear();
    mTexCBuffer.clear();
    mMaterialBuffer.clear();
    if (mDisplayID!=0){
    	glDeleteLists(mDisplayID, 1);
    }
}

void TMesh::display(){
	std::map<std::string, std::vector<TPolygonFace> >::iterator it;
	for ( it=mFaceBuffer.begin() ; it != mFaceBuffer.end(); it++ ){
		
		mDisplayID=renderMesh(mVertexBuffer,
				mNormalBuffer,
				(*it).second,
				mTexCBuffer,
				mMaterialBuffer[(*it).first],
				getImage(),mDisplayID);	
		
//		tri::matrix mat;
//		get_model_matrix(&mat);
//		renderMeshCellShaded(mVertexBuffer,
//				mNormalBuffer,
//				(*it).second,
//				mTexCBuffer,
//				*getMaterialPointer(),
//				getImage(),&mat);	
	}
	
}

GLuint TMesh::renderMesh(std::vector<tri::TVector>& VertexBuffer,
    					  std::vector<tri::TVector>& NormalBuffer,
    							std::vector<TPolygonFace> &Faces,
    							std::vector<TTexCoord>& TexCBuffer,
							    tri::TMaterial& Material,
							    std::string Texture,
							    GLuint mDisplayID){
	
	Material.apply();
	if(mDisplayID==0){
		//mDisplayID = glGenLists(1);
	//	glNewList(mDisplayID, GL_COMPILE);

		//tri::applyTexture(Texture);
		for (unsigned int face_ind =0 ; face_ind < Faces.size() ; face_ind++){
			
			TPolygonFace face = Faces[face_ind];
			unsigned int vert_count = face.size();
			
			glBegin(GL_POLYGON);
			for (unsigned int v=0;v<vert_count; v++){
				
				tri::TVector vert = VertexBuffer[face.getVertex(v)];
				tri::TVector norm = NormalBuffer[face.getNormal(v)];
				TTexCoord tc = TexCBuffer[face.getTexCoord(v)];
				glTexCoord2f(tc.u,tc.v);
				glNormal3f(norm.x,norm.y,norm.z);
				glVertex3f(vert.x,vert.y,vert.z);
				
			}
			
			glEnd();
			
		}
		//glEndList();	
	}else{
		glCallList(mDisplayID);
	}
	
	return mDisplayID;
}

static float dotProduct(tri::TVector v1, tri::TVector v2){
	
	return (v1.x * v2.x) + (v1.y * v2.y) + (v1.z * v2.z);
}

void TMesh::renderMeshCellShaded(std::vector<tri::TVector>& VertexBuffer,
    					  std::vector<tri::TVector>& NormalBuffer,
    							std::vector<TPolygonFace> &Faces,
    							std::vector<TTexCoord>& TexCBuffer,
							    tri::TMaterial& Material,
							    std::string Texture,tri::TMatrix* mat){
	
	/**
	 * render cell shaded shadow
	 */
	glDisable(GL_TEXTURE_2D);
	glEnable(GL_TEXTURE_1D);
	GLboolean lighting = glIsEnabled(GL_LIGHTING);
	glDisable(GL_LIGHTING);
	TMesh::bindShader();
	glColor4f(1,1,1,0.4);
    for (unsigned int face_ind =0 ; face_ind < Faces.size() ; face_ind++){
    		
		TPolygonFace face = Faces[face_ind];
		unsigned int vert_count = face.size();
		
		glBegin(GL_POLYGON);
		for (unsigned int v=0;v<vert_count; v++){
			
			tri::TVector vert = VertexBuffer[face.getVertex(v)];
			tri::TVector norm = NormalBuffer[face.getNormal(v)];
			tri::TVector TmpVector;
			//std::cout<<norm<<std::endl;
			tri::multVMat(mat,&norm,&TmpVector);
			TmpVector.normalize();
			//std::cout<<TmpVector<<std::endl;
			tri::TVector light_dir(0,2,1);
			//light_dir.normalize();
			//std::cout<<light_dir<<std::endl;
			
		    float TempShade = dotProduct(TmpVector,light_dir);
		    glTexCoord1f(tri::utils::ABS(TempShade));
			//std::cout<<TempShade<<std::endl;
			glVertex3f(vert.x,vert.y,vert.z);
			
		}
		
		glEnd();
		
	}
    glDisable(GL_TEXTURE_1D);
	glEnable(GL_TEXTURE_2D);
	
	/**
	 * DRAW OUTLINE
	 */
	
	glPolygonMode (GL_BACK, GL_LINE);						// Draw Backfacing Polygons As Wireframes ( NEW )
	glLineWidth (3);								// Set The Line Width ( NEW )
	glCullFace (GL_FRONT);									// Don't Draw Any Front-Facing Polygons ( NEW )
	glDepthFunc (GL_LEQUAL);								// Change The Depth Mode ( NEW )
	glColor3f(1,0,0);
	glDisable(GL_TEXTURE_2D);
//	renderMesh(VertexBuffer,
//			NormalBuffer,
//			Faces,
//			TexCBuffer,
//			Material,
//			Texture);
	glPolygonMode (GL_BACK, GL_FILL);
	glCullFace (GL_BACK);
	glEnable(GL_TEXTURE_2D);
	if(lighting){
		glEnable(GL_LIGHTING);
	}
}

TMesh* TMesh::loadObjMesh(const char* p_filename){
  std::string line;
  std::ifstream myfile (p_filename);
  std::string object_name = "";
  
  if (myfile.is_open())
  {
	TMesh* mesh = new TMesh();
    while ( myfile.good() )
    {
      getline (myfile,line);
      if(tri::utils::startsWith(line,"g ")){
    	  char buffer[50];
    	  sscanf(line.c_str(),"g %s",buffer);
    	  object_name = std::string(buffer);
    	  mesh->mFaceBuffer[object_name] = std::vector<TPolygonFace>();
    	  mesh->mMaterialBuffer[object_name] = tri::TMaterial();
	  }else if(tri::utils::startsWith(line,"v ")){
    	  float x,y,z;
    	  sscanf(line.c_str(),"v %f %f %f",&x,&y,&z);
    	  mesh->mVertexBuffer.push_back(tri::TVector(x,y,z));
    	  
      }else if(tri::utils::startsWith(line,"vn ")){
    	  float x,y,z;
    	  sscanf(line.c_str(),"vn %f %f %f",&x,&y,&z);
    	  mesh->mNormalBuffer.push_back(tri::TVector(x,y,z));
    	  
      }else if(tri::utils::startsWith(line,"vt ")){
    	  float x,y;
    	  sscanf(line.c_str(),"vt %f %f",&x,&y);
    	  mesh->mTexCBuffer.push_back(TTexCoord(x,1-y));
    	  
      }else if(tri::utils::startsWith(line,"f ")){
    	  std::vector<std::string> split_str;
    	  tri::utils::split(split_str,line,' ');
    	  unsigned int x,y,z;
    	  TPolygonFace face;
    	  //std::cout<<split_str.size(); 
    	  for (unsigned int i = 1; i < split_str.size() ; i++){
	    	  sscanf(split_str[i].c_str(),"%u/%u/%u",&x,&y,&z);
	    	  face.addData(x-1,y-1,z-1);
    	  }
    	  mesh->mFaceBuffer[object_name].push_back(face);
      }
    }
    myfile.close();
    return mesh;
  }	
  
  return NULL;
}


void TMesh::setModelTexture(std::string image, std::string model){
	std::map<std::string, tri::TMaterial >::iterator it;
	for ( it=mMaterialBuffer.begin() ; it != mMaterialBuffer.end(); it++ ){
		if(model == "" || (*it).first == model){
			(*it).second.setImage(image);
		}
	}
}

/**
void setModel(std::string model);
void setModelTexture(std::string, std::string);

bool Model3D::loadModel(std::string key,std::string _file){
	
	if(_file == ""){
		_file = key;
	}
	if(!tri::sys::exists(_file)){
		tri::LOG_SEVERE("Unable to locate model file...:"+_file );
		return false;
	}
	
	int count = 0;
	tri::LOG_INFO("Loading Model from "+_file+" ..." );
	TMesh* objs = object_3d::load3dsMesh(count,_file.c_str());
	if (count == 0 || objs == NULL){
		tri::LOG_SEVERE("Unable to load model from file...:"+_file );
		return false;
	}
	
	if (Model3D::exists(key)){
		deleteModel(key);
	}
	
	mModelList[key] = new ObjectGroup();
	mModelList[key]->mObjectCount = count;
	mModelList[key]->mObjects = objs;
	for (int i = 0;i<mModelList[key]->mObjectCount;i++){
		mModelList[key]->mObjects[i].calculateNormals();
	}
	return true;
}

void Model3D::setModel(std::string model){
	this->mModel = model;
}

void TModel3D::deleteModel(std::string key){
	if (TModel3D::exists(key)){
		TMesh* group = mModelList[key];
		delete group;
		mModelList.erase(key);
	}
}

bool TModel3D::exists(std::string key){
	std::map<std::string,TMesh*>::iterator it;
	for ( it=mModelList.begin() ; it != mModelList.end(); it++ ){
		if ( (*it).first == key ){
			return true;
		}
	}
}
*/

}//close namespace tri